#region --- License & Copyright Notice ---
/*
ConsoleFx CommandLine Processing Library

Copyright (c) 2006-2012 Jeevan James
All rights reserved.

The contents of this file are made available under the terms of the
Eclipse Public License v1.0 (the "License") which accompanies this
distribution, and is available at the following URL:
http://opensource.org/licenses/eclipse-1.0.txt

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

By using this software in any fashion, you are agreeing to be bound by the
terms of the License.
*/
#endregion

using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;

using ConsoleFx.Resources;

namespace ConsoleFx.Capture
{
    public sealed class ConsoleCapture
    {
        private readonly string _fileName;
        private readonly string _arguments;

        public ConsoleCapture(string fileName)
            : this(fileName, null)
        {
        }

        public ConsoleCapture(string fileName, string arguments)
        {
            if (fileName == null)
                throw new ArgumentNullException("fileName");
            if (!File.Exists(fileName))
                throw new ArgumentException(CaptureMessages.FileDoesNotExist.Fmt(CultureInfo.CurrentCulture, fileName), "fileName");
            _fileName = fileName;
            _arguments = arguments;
        }

        //Primary action method for this class. Starts the specified application as a console app
        //and captures the output and (optionally) the error output.
        public ConsoleCaptureResult Start(bool captureError = false)
        {
            using (var process = new Process())
            {
                //Need to enforce a command-line app as best we can
                process.StartInfo.FileName = _fileName;
                process.StartInfo.Arguments = _arguments;
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.ErrorDialog = false;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                if (captureError)
                    process.StartInfo.RedirectStandardError = true;

                if (!process.Start())
                    throw new ConsoleCaptureException(ConsoleCaptureException.Codes.ProcessStartFailed, CaptureMessages.ProcessStartFailed,
                        _fileName);

                string outputMessage, errorMessage = string.Empty;
                int exitCode = captureError
                    ? CaptureOutputAndError(process, out outputMessage, out errorMessage) : CaptureOutput(process, out outputMessage);
                return new ConsoleCaptureResult(exitCode, outputMessage, errorMessage);
            }
        }

        //Used to capture output if error output is not required
        private static int CaptureOutput(Process process, out string outputMessage)
        {
            outputMessage = process.StandardOutput.ReadToEnd();

            if (!process.HasExited)
                process.WaitForExit();

            return process.ExitCode;
        }

        //Used to capture both output and error output. This code is more complicated and is based
        //on the CodeProject article by Andrew Tweddle:
        //http://www.codeproject.com/KB/string/CommandLineHelper.aspx
        private static int CaptureOutputAndError(Process process, out string outputMessage, out string errorMessage)
        {
            Func<string> outputReader = process.StandardOutput.ReadToEnd;
            Func<string> errorReader = process.StandardError.ReadToEnd;

            IAsyncResult outputResult = outputReader.BeginInvoke(null, null);
            IAsyncResult errorResult = errorReader.BeginInvoke(null, null);

            if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
            {
                //WaitHandle.WaitAll fails on single-threaded apartments. Poll for completion instead.
                while (!(outputResult.IsCompleted && errorResult.IsCompleted))
                    Thread.Sleep(100);
            } else
            {
                var waitHandles = new[] { outputResult.AsyncWaitHandle, errorResult.AsyncWaitHandle };
                if (!WaitHandle.WaitAll(waitHandles))
                    throw new ConsoleCaptureException(ConsoleCaptureException.Codes.ProcessAborted, CaptureMessages.ProcessAborted);
            }

            outputMessage = outputReader.EndInvoke(outputResult);
            errorMessage = errorReader.EndInvoke(errorResult);

            if (!process.HasExited)
                process.WaitForExit();

            return process.ExitCode;
        }

        public string Arguments
        {
            get { return _arguments; }
        }

        public string FileName
        {
            get { return _fileName; }
        }

        //Static shortcut for capturing console output.
        public static ConsoleCaptureResult Start(string fileName, string arguments = null, bool captureError = false)
        {
            return new ConsoleCapture(fileName, arguments).Start(captureError);
        }
    }
}